//////////////////////////////////////////////////////////////////
// MattiseOS Kernel
// Date: 2008
// Author: Matthew Iselin
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//////////////////////////////////////////////////////////////////

// Defines
#define KERNEL_MAIN_FILE	// used to stop some references
//#define KERNEL_TESTING		// used when testing out a new feature to remove
							// common initialization stuff (tasking et al)
//#define NE2K_TESTING		// testing out the NE2K card

// Includes

// basic includes
#include <mattise.h>
#include <stdint.h>
#include <multiboot.h>

// kernel core includes - init et al
#include <core/dts.h>
#include <core/pit.h>
#include <core/portio.h>
#include <core/intrcommon.h>

// process code
#include <process/common.h>
#include <exec/elf.h>

// I/O
#include <screen/conio.h>
#include <kernio/io.h>
#include <vfs/vfs.h>

// devices
#include <net/ne2k/ne2k.h>

// memory
#include <mem/util.h>
#include <mem/page.h>
#include <mem/heap.h>

// utility
#include <string.h>

// extra system includes
#include <sys/calls.h>
#include <message/message.h>

// Variables

// the module list
module_t* mods;

// module count
uint32_t modcount;

// the end of the kernel image
extern char __end;

// the total available memory
uint32_t total_avail_mem = 0;

// the kernel process (so we can make children of it)
int kernprocess_parentid = 0;

// Prototypes

// the ramfs install function
void ramfs_install( uint32_t );

// Functions

// null device read function (only relevant one)
size_t null_sys_read( int32_t UNUSED(fd), void* buff, size_t count )
{
	// fill the buffer with zeroes
	memset( buff, 0, count );

	// return the count again
	return count;
}

// cleans up dead processes and other things
int can_clean_threads = 1;
void cleanup_thread( int UNUSED(argc), char* UNUSED(argv[]) )
{
	dprintf( "[SYS  ] cleanup_thread begins [%d]\n", getpid() );

	while( 1 )
	{
		// do the clean up - remove dead tasks, etc...
		if(can_clean_threads)
		{
			asm volatile("cli");
			kCleanUpScheduler();
			asm volatile("sti");
		}

		// give up the timeslice when we return
		kSchedule( 0 );
		asm volatile( "pause" );
	}
}

// this is the main kernel thread - it handles all I/O queues and other fun details
void kern_thread( int UNUSED(argc), char* UNUSED(argv[]) )
{
	dprintf( "[SYS  ] kern_thread begins [%d]\n", getpid() );

	// loop forever, always give up the timeslice when done
	while( 1 )
	{
		// handle any IO waiting
		HandleIO();

		// give up the timeslice if we return
		kSchedule( 0 );
		asm volatile( "pause" );
	}
}

int paging_ready_to_go = 0;

// this is the idle thread that runs while no other task is running
void idle( int UNUSED(argc), char* UNUSED(argv[]) )
{
	dprintf( "[SYS  ] idle begins [%d]\n", getpid() );

	paging_ready_to_go = 1;

    // grab the CR3 value (it's the kernel directory)
    uint32_t cr3;
    asm volatile( "mov %%cr3, %0" : "=r" (cr3) );

    // descriptor for open calls
    int32_t fd;

    // load the ramdisk image (assuming it exists)
    size_t z;
    for( z = 0; z < modcount; z++ )
    {
    	// grab the module name
    	char* modname = (char*) mods[z].string;

    	// check if it's the ramdisk
    	if( strcmp( modname + (strlen( modname ) - strlen( "ramfs.bin" )), "ramfs.bin" ) == 0 )
    	{
    		// install the RAMFS
			ramfs_install( mods[z].mod_start );
			break;
    	}
    }

    /** load drivers that are in the ramfs (there must always be a specific set of drivers) **/

    // the keyboard driver
	fd = sys_open( "/ramfs/kb.ko", 0 );
	struct stat s;
	sys_stat( fd, &s );
	char* ptr = (char*) malloc( s.st_size + 1 );
	int32_t r = sys_read( fd, ptr, s.st_size );
	int32_t load_res = LoadElf( cr3, (uint32_t) ptr, 1, 0, 0, "/ramfs/kb.ko", 0 );
	free( ptr );
	sys_close( fd );

    // the TTY driver (tty = terminal i/o)
	fd = sys_open( "/ramfs/tty.ko", 0 );
	sys_stat( fd, &s );
	ptr = (char*) malloc( s.st_size + 1 );
	r = sys_read( fd, ptr, s.st_size );
	load_res = LoadElf( cr3, (uint32_t) ptr, 1, 0, 0, "/ramfs/tty.ko", 0 );
	free( ptr );
	sys_close( fd );

	// the ATA driver (ie, hard drives)
	fd = sys_open( "/ramfs/ata.ko", 0 );
	sys_stat( fd, &s );
	ptr = (char*) malloc( s.st_size + 1 );
	r = sys_read( fd, ptr, s.st_size );
	load_res = LoadElf( cr3, (uint32_t) ptr, 1, 0, 0, "/ramfs/ata.ko", 0 );
	free( ptr );
	sys_close( fd );

	// the FAT driver (wootwiki much?)
	fd = sys_open( "/ramfs/fat.ko", 0 );
	sys_stat( fd, &s );
	ptr = (char*) malloc( s.st_size + 1 );
	r = sys_read( fd, ptr, s.st_size );
	load_res = LoadElf( cr3, (uint32_t) ptr, 1, 0, 0, "/ramfs/fat.ko", 0 );
	free( ptr );
	sys_close( fd );

	// need a fake argv for the shell
	char** newargv = (char**) malloc( sizeof( char* ) * 2 );
	newargv[0] = 0;

	// install the NE2K cards
	dprintf("[NE2K ] installing...\n");
	ne2k_init();
	dprintf("[NE2K ] installed\n");

	// totally ready to go now
	printf("Mattise Kernel startup is complete.\n\n");

	// tell how much ram there is now
	dprintf("[MEM  ] after load memory usage: %d KB\n", kGetAvailMem() / 1024);

    // run the shell
    load_res = LoadStaticBinaryFromFile( "/ramfs/minish", cr3, TF_NONE, 0, newargv );

	// loop forever, always give up the timeslice
	while( 1 )
	{
		kSchedule( 0 );
		asm volatile( "pause" );
	}
}

// kmain: entry point to the kernel
void kmain( multiboot_info_t* mboot_ptr )
{
	// clear the screen
	kClearScreen( 0, 0 );

    // get the module list pointers
    mods = (module_t*) mboot_ptr->mods_addr;
    modcount = mboot_ptr->mods_count;

	// grab the memory map
	memory_map_t* memmap = (memory_map_t*) mboot_ptr->mmap_addr;

	// setup custom handling
	kSetupCustomIntrHandlers();

	// find out how much memory we can use
	uint32_t availmem = 0;
	uint32_t mmap_off;
	for( mmap_off = 0; mmap_off < (mboot_ptr->mmap_length / sizeof( memory_map_t )); mmap_off++ )
		availmem += memmap[mmap_off].length_low;	// also keep invalid sections, they'll become unusable
													// later

	// setup the page allocator
	total_avail_mem = availmem;
	dprintf("[PAGE ] setting up page allocator [availmem=%d KB]... ", availmem / 1024);
	kInitPageAlloc( availmem );

	// load the kernel symbol table early
	InitKernelSymbolTable(mboot_ptr);

	// find out the end of the kernel space
	uint32_t kernel_virt_end = (uint32_t) &__end;
	uint32_t kernel_virt_end_page = (kernel_virt_end & ~0xFFF) + 0x1000;

	// set the kernel space as used
	kSetPages( 0x100000, kernel_virt_end_page, 1 );

	// parse it, set any invalid areas as used
	for( mmap_off = 0; mmap_off < (mboot_ptr->mmap_length / sizeof( memory_map_t )); mmap_off++ )
	{
		// 0x1 means valid memory
		if( memmap[mmap_off].type == 0x1 )
		{
#if 0
			// only map areas ABOVE the end of the kernel
			uint32_t base = memmap[mmap_off].base_addr_low;
			uint32_t upper_limit = (memmap[mmap_off].base_addr_low + memmap[mmap_off].length_low);
			if( base <= kernel_virt_end_page )
			{
				// the low section is before the end, check that the upper is also below
				if( upper_limit <= kernel_virt_end_page )
				{
					// this isn't mappable!
				}
				else
				{
					// map in the area FROM the end of the kernel to the upper limit
					//dprintf("adding pages from %x to %x\n", kernel_virt_end_page, upper_limit);

					// check that the upper limit doesn't conflict with the modules
					if(mods[0].mod_start < upper_limit)
					{
						// assume there's memory above the limit
						kSetPages(kernel_virt_end_page, mods[0].mod_start, 0);
						kSetPages(mods[modcount-1].mod_end + 0x1000, upper_limit, 0);
					}
					else
					{
						// map it in
						kSetPages( kernel_virt_end_page, upper_limit, 0 );
					}
				}
			}
			else
			{
				// map it in
				kSetPages( base, upper_limit, 0 );
			}
#endif
		}
		else
		{
			// set it as taken/used
			uint32_t base = memmap[mmap_off].base_addr_low;
			uint32_t upper_limit = (memmap[mmap_off].base_addr_low + memmap[mmap_off].length_low);
			kSetPages( base, upper_limit, 1 );
		}
	}

	// set the modules as taken/used
	uint32_t mod;
	for(mod = 0; mod < modcount; mod++)
	{
		kSetPages(mods[mod].mod_start, mods[mod].mod_end, 1);
	}

	// set the first MB as taken/used
	kSetPages(0, 0x100000, 1);
	dprintf("OK\n");

	// setup paging
#if !defined(KERNEL_TESTING) && !defined(NE2K_TESTING)
	dprintf("[PAGE ] setting up paging... ");
	kInitPaging();
	dprintf("OK\n");
#endif

	// tell how much ram there is initially
	dprintf("[MEM  ] before load memory usage: %d KB\n", kGetAvailMem() / 1024);

	// setup the GDT
	dprintf("[GDT  ] setting up GDT... ");
	kInitGDT();
	dprintf("OK\n");

	// setup the TSS
	dprintf("[TSS  ] setting up TSS... ");
	kInitTSS();
	dprintf("OK\n");

	// finish installing the GDT
	dprintf("[GDT  ] final setting up GDT... ");
	kFinishGDTSetup();
	dprintf("OK\n");

	// load the TSS
	dprintf("[TSS  ] loading TSS... ");
	kLoadTSS();
	dprintf("OK\n");

	// setup the IDT
	dprintf("[IDT  ] setting up IDT... ");
	kInitIDT();
	dprintf("OK\n");

	// install the IRQs
	dprintf("[IRQ  ] setting up IRQs... ");
	kSetupIRQs();
	dprintf("OK\n");

	// setup the PIT
	dprintf("[PIT  ] setting up PIT... ");
	kInitPIT();
	dprintf("OK\n");

    // setup the ELF loading system
	dprintf("[ELF  ] setting up elf systems... ");
    InitElfSystems( mboot_ptr );
	dprintf("OK\n");

	// setup the kernel heap
	dprintf("[KHEAP] setting up kernel heap... ");
	malloc( 1 );
	dprintf("OK\n");

	// set up the kernel stack allocator
	dprintf("[KSTK ] setting up kernel stack heap... ");
	setup_kernel_stack_heap( total_avail_mem );
	dprintf("OK\n");

	// allocate space straight away for the module table
    mods = (module_t*) malloc( sizeof( module_t ) * ( mboot_ptr->mods_count + 1 ) );
	memcpy( mods, (void*) mboot_ptr->mods_addr, sizeof( module_t ) * mboot_ptr->mods_count );

	// setup the device manager
	dprintf("[DEVM ] setting up device manager... ");
	SetupDevMan();
	dprintf("OK\n");

	// setup the VFS
	dprintf("[VFS  ] vfs init... ");
	//kInitializeVFS(); TODO: write the VFS
	dprintf("OK\n");

	// setup COM1 for dprintf
	#define PORT 0x3f8
	dprintf("[COM1 ] setting up com1... ");
	outb(PORT + 1, 0x00);    // Disable all interrupts - only polled I/O
	outb(PORT + 3, 0x80);    // Enable DLAB (set baud rate divisor)
	outb(PORT + 0, 0x03);    // Set divisor to 3	(lo byte) 38400 baud
	outb(PORT + 1, 0x00);    // 					(hi byte)
	outb(PORT + 3, 0x03);    // 8 bits, no parity, one stop bit
	outb(PORT + 2, 0xC7);    // Enable FIFO, clear them, with 14-byte threshold
	outb(PORT + 4, 0x0B);    // RTS/DSR set
	dprintf("OK\n");

	// print the welcome message
	/*printf( "Welcome to Mattise 3.0. This is an alpha version and is likely to crash.\nI warned you ;)! If it does crash, my win you >=D...\n\n" );
    printf( "The lead programmer (the only one, really) apologises for any insane\n" );
    printf( "humour that may make you laugh if this OS crashes (which really isn't\n" );
    printf( "all that funny). Please remember we also don't like the OS crashing, because\n" );
	printf( "segfaults hurt the computer (please see http://www.xkcd.com/371/ :P)\n" );
    printf( "Except around these parts we call them 'memory accidents' because segfault is\n" );
    printf( "a very boring and unexciting word.\n\n" );*/

	printf( "Welcome to Mattise 3.0. This version is an alpha version and may crash.\nIf it does crash, please " );
	printf( "tell me by submitting a bug report at\nhttp://www.sf.net/projects/mattise." );
	printf( "\n\nBecause it is an alpha version, functionality is reduced. This release does not directly represent the ");
	printf( "beta or final releases of Mattise.\n\n" );

#ifdef KERNEL_TESTING
	// testing log: trying out SMP for Mattise 3.0, not Mattise 3.0

    /** TRYING OUT SMP STUFF **/

    // the start of the EBDA area is at 0x40E
    uint16_t* ebda_start_ptr = (uint16_t*) 0x40E;
    uint32_t ebda_start_loc = (*ebda_start_ptr) << 4;

    // search the 1kb space above there
    uint32_t addr = ebda_start_loc;
    for( ; addr < (ebda_start_loc + 1024); addr += 16 )
    {
    	if( strcmp( "_MP_", (char*) addr ) == 0 )
			break;
    }
    if( addr == (ebda_start_loc + 1024) )
    {
		dprintf( "not found in ebda...\n" );
    }

    dprintf( "--\n" );

    // search from 0xf0000 to 0x100000 for the tables
    for( addr = 0xf0000; addr < 0x100000; addr += 16 )
    {
    	uint32_t sig = *((uint32_t*) addr);
    	if( sig == 0x5F504D5F )
			break;
    }
    if( addr == 0x100000 )
    {
    	dprintf( "not found in bios area...\n" );
    	printf( "MP Floating Pointer Structure not found!\n" );
    	while( 1 );
    }
    printf( "Found!\n" );

    // grab a better pointer to it
    struct _mpfps {
		char		signature[4];
		uint32_t	physaddr;
		char		length;
		char		spec_rev;
		char		csum;
		char		mpfeat_b1;
		uint32_t	mpfeat;
    } __attribute__((packed));
    struct _mpfps* mpfps = (struct _mpfps*) addr;

    printf( "Sig: %c%c%c%c\n", mpfps->signature[0], mpfps->signature[1], mpfps->signature[2], mpfps->signature[3] );
    printf( "Physical addr: %x\n", mpfps->physaddr );

    if( mpfps->physaddr == 0 )
	{
		dprintf( "invalid MP config table!\n" );
		printf( "No valid MP config tables!\n" );
		while( 1 );
	}

	// the mp configuration table header structure
	struct _mpcfghead {
		char		signature[4];
		short		tbl_len;
		char		spec_rev;
		char		csum;
		char		oemid[8];
		char		productid[12];
		uint32_t	oemtblptr;
		short		oemtblsz;
		short		entcount;
		uint32_t	memmaped_apic;
		short		extlen;
		char		extcsum;
		char		rsvd;
	} __attribute__((packed));
	struct _mpcfghead* cfghead = (struct _mpcfghead*) mpfps->physaddr;

	// verify it
	if( ! (	cfghead->signature[0] == 'P' &&
			cfghead->signature[1] == 'C' &&
			cfghead->signature[2] == 'M' &&
			cfghead->signature[3] == 'P' ) )
	{
		dprintf( "invalid mp config table header\n" );
		printf( "No valid header!\n" );
		while( 1 );
	}

	// now, grab a pointer to the space after that and find any processor entries
	char* entptr = (char*) (mpfps->physaddr + sizeof( struct _mpcfghead ));
	uint32_t e = 0; //(uint32_t) entptr;
	int entryid;
	for( entryid = 0; entryid < cfghead->entcount; entryid++ ) //(cfghead->tbl_len - sizeof( struct _mpcfghead )); )
	{
		// what type is this?
		switch( entptr[e] )
		{
			case 0:
				printf( "CPU entry found!\n" );

				// get a structure for it now
				struct cpuent {
					char ent_type; // = 0
					char apicid;
					char apicver;
					unsigned int cpu_flags_usable : 1;
					unsigned int cpu_flags_bootstrap : 1;
					unsigned int cpu_flags_rsvd : 6;
					unsigned int cpu_sig_stepping : 4;
					unsigned int cpu_sig_model : 4;
					unsigned int cpu_sig_family : 4;
					unsigned int cpu_sig_rest : 20;
					unsigned int feature_flags;
					unsigned int rsvd1;
					unsigned int rsvd2;
				} __attribute__((packed));
				struct cpuent* cpu = (struct cpuent*) (((uint32_t) entptr) + e);

				// only work if it isn't the bootstrap cpu (ie, the one running this code)
				if( cpu->cpu_flags_bootstrap == 0 )
				{
					//
					printf( "booting additional processor...\n" );
				}
				else
					printf( "already booted this processor\n" );

				e += 20;
				break;
			case 1:
				//printf( "Bus entry found!\n" );
				e += 8;
				break;
			case 2:
				//printf( "I/O APIC entry found!\n" );
				e += 8;
				break;
			case 3:
				//printf( "I/O Interrupt Assignment entry found!\n" );
				e += 8;
				break;
			case 4:
				//printf( "Local Interrupt Assignment entry found!\n" );
				e += 8;
				break;
		}
	}
	printf( "done much?\n" );
#else

	// setup mailboxes
	dprintf("[MAIL ] init mailboxes... ");
	InitMailboxes();
	dprintf("OK\n");

    // install system call handling
	dprintf("[SYSC ] init system calls... ");
    kInstallSystemCalls();
	dprintf("OK\n");

    // setup multitasking
	dprintf("[TASK ] init multitasking... ");
    kInitTasking();
	dprintf("OK\n");

	// gotten this far
    dprintf( "[INFO ] basic kernel init complete\n" );

    // grab the CR3 value (it's the kernel directory)
    uint32_t cr3;
    asm volatile( "mov %%cr3, %0" : "=r" (cr3) );

    // create the idle process
    uint32_t newcr3 = cr3;
    int32_t newproc = kernprocess_parentid = kCreateProcess( (uint32_t) idle, TF_NONE, newcr3, 1, 0, 0 );

    // map the vga text framebuffer as user accessible memory
	ChangeMapping( 0xFFFFF000, 0xb8000, 0, PF_PRESENT | PF_READWRITE | PF_USER | PF_KERNELPAGE );

    // and make the main kernel thread (and cleanup threads)
    // kCreateThread( newproc, (uint32_t) kern_thread, TF_NONE, 20, 0, 0, 0 );
    kCreateThread( newproc, (uint32_t) cleanup_thread, TF_NONE, 10, 0, 0, 0 );

	dprintf("[INFO ] threads created\n");

	// install the null device
	InstallDevice( "/dev/null", 0, (uint32_t) null_sys_read, 0, 0, 0, 0, 0, sizeof( uint8_t ) );

    // permit task switching now
	kSchedule(0);
#endif

	// loop forever
	while( 1 ) asm volatile( "hlt" );
}
